package com.lagou.springbootweb.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.retry.RetryNTimes;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * 学习使用开源zookeeper客户端 Curator操作
 *
 * 代理模式：实现 DataSource
 *
 * 需求：jdbc连接配置放置zookeeper上， 当修改zookeeper上面的配置，连接池druid需要重新初始化连接，同时释放之前的连接
 */

public class DynamicConfigDatasource implements DataSource {

    /**
     * 配置信息
     */
    private Map<String, String> configMap = new HashMap<>();

    /**
     * 监听的接节点
     */
    private static final String CONFIG_PREFIX = "/config";

    private CuratorFramework zkClient;


    /**
     * 真实的连接信息
     */
    private volatile DruidDataSource dataSource;

    public static DynamicConfigDatasource create(String zookeeperAddres) {
        return new DynamicConfigDatasource(CuratorFrameworkFactory.newClient(zookeeperAddres, new RetryNTimes(3, 1000)));
    }

    /**
     * 构造方法
     */
    private DynamicConfigDatasource(CuratorFramework zkClient) {
        this.zkClient = zkClient;

        if (!zkClient.getState().equals(CuratorFrameworkState.STARTED)) {
            zkClient.start();
        }

        // 保存当前的数据源配置信息
        try {
            saveDatasourceConfig();

            // 进行初始化真实的数据源
            initDatasource();

            // 进行启动监听
            startListener();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void startListener() throws Exception {
        PathChildrenCache watcher = new PathChildrenCache(zkClient, CONFIG_PREFIX, true);
        watcher.getListenable().addListener((curatorFramework, event) -> {
            if (event.getType() != PathChildrenCacheEvent.Type.CHILD_UPDATED) {
                return;
            }

            System.out.println("数据源配置信息更新:" + new String(event.getData().getData()));

            // 重置当前的配置信息
            saveDatasourceConfig();

            // 获取当前系统中的数据源
            DruidDataSource currentDataSource = this.dataSource;

            // 重置当前的数据源
            initDatasource();

            if (currentDataSource != null) {
                currentDataSource.close();
                System.out.println("close the old datasource");
            }

        });

        watcher.start();

        System.out.println("完成数据源的监听操作");
    }

    private void initDatasource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(configMap.get("driverClassName"));
        dataSource.setUrl(configMap.get("url"));
        dataSource.setUsername(configMap.get("username"));
        dataSource.setPassword(configMap.get("password"));

        // 切换新的 数据域
        this.dataSource = dataSource;
        System.out.println("initDatasource");
    }

    /**
     * 获取zk中的jdbc配置信息，保存到内存
     * @throws Exception
     */
    private void saveDatasourceConfig() throws Exception {
        List<String> childrenNames = zkClient.getChildren().forPath(CONFIG_PREFIX);
        for (String childrenName : childrenNames) {
            String value = new String(zkClient.getData().forPath(CONFIG_PREFIX + "/" + childrenName));

            System.out.println(childrenName + "--------------" + value);
            configMap.put(childrenName, value);
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return dataSource.getConnection(username, password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
